/* eslint-disable no-undef */
let errorCallbackCount: any = 0;

// 常量
const DEFAULT_VALIDATE =
  'QjGAuvoHrcpuxlbw7cp4WnIbbjzG4rtSlpc7EDovNHQS._ujzPZpeCInSxIT4WunuDDh8dRZYF2GbBGWyHlC6q5uEi9x-TXT9j7J705vSsBXyTar7aqFYyUltKYJ7f4Y2TXm_1Mn6HFkb4M7URQ_rWtpxQ5D6hCgNJYC0HpRE7.2sttqYKLoi7yP1KHzK-PptdHHkVwb77cwS2EJW7Mj_PsOtnPBubTmTZLpnRECJR99dWTVC11xYG0sx8dJNLUxUFxEyzTfX4nSmQz_T5sXATRKHtVAz7nmV0De5unmflfAlUwMGKlCT1khBtewlgN5nHvyxeD8Z1_fPVzi9oznl-sbegj6lKfCWezmLcwft8.4yaVh6SlzXJq-FnSK.euq9OBd5jYc82ge2_hEca1fGU--SkPRzgwkzew4O4qjdS2utdPwFONnhKAIMJRPUmCV4lPHG1OeRDvyNV8sCnuFMw7leasxIhPoycl4pm5bNy70Z1laozEGJgItVNr3'; // 默认validate
const FALLBACK_LANG: any = {
  'zh-CN': '前方拥堵，已自动跳过验证',
  en: 'captcha error，Verified automatically',
};
const CACHE_MIN = 1000 * 60; // 缓存时长单位，1分钟
const REQUEST_SCRIPT_ERROR = 502;

const RESOURCE_CACHE: any = {};

// 工具函数
function loadScript(src: any, cb: any) {
  const head: any = document.head || document.getElementsByTagName('head')[0];
  const script: any = document.createElement('script');

  cb = cb || function () {};

  script.type = 'text/javascript';
  script.charset = 'utf8';
  script.async = true;
  script.src = src;

  if (!('onload' in script)) {
    script.onreadystatechange = function () {
      if (this.readyState !== 'complete' && this.readyState !== 'loaded') {
        return;
      }
      this.onreadystatechange = null;
      cb(null, script); // there is no way to catch loading errors in IE8
    };
  }

  script.onload = function () {
    this.onerror = this.onload = null;
    cb(null, script);
  };
  script.onerror = function () {
    // because even IE9 works not like others
    this.onerror = this.onload = null;
    cb(new Error('Failed to load ' + this.src), script);
  };

  head.appendChild(script);
}

function joinUrl(protocol: any, host: any, path: any) {
  protocol = protocol || '';
  host = host || '';
  path = path || '';
  if (protocol) {
    protocol = protocol.replace(/:?\/{0,2}$/, '://');
  }
  if (host) {
    const matched = host.match(/^([-0-9a-zA-Z.:]*)(\/.*)?/);
    host = matched[1];
    path = (matched[2] || '') + '/' + path;
  }
  !host && (protocol = '');

  return protocol + host + path;
}

function setDomText(el: any, value: any) {
  if (value === undefined) {
    return;
  }
  const nodeType = el.nodeType;
  if (nodeType === 1 || nodeType === 11 || nodeType === 9) {
    if (typeof el.textContent === 'string') {
      el.textContent = value;
    } else {
      el.innerText = value;
    }
  }
}

function queryAllByClassName(selector: any, node: any) {
  node = node || document;
  if (node.querySelectorAll) {
    return node.querySelectorAll(selector);
  }
  if (!/^\.[^.]+$/.test(selector)) {
    return [];
  }
  if (node.getElementsByClassName) {
    return node.getElementsByClassName(selector);
  }

  const children = node.getElementsByTagName('*');
  let current;
  const result = [];
  const className = selector.slice(1);
  for (let i = 0, l = children.length; i < l; i++) {
    current = children[i];
    if (~(' ' + current.className + ' ').indexOf(' ' + className + ' ')) {
      result.push(current);
    }
  }
  return result;
}

function assert(condition: any, msg: any) {
  if (!condition) {
    throw new Error('[NECaptcha] ' + msg);
  }
}

function isInteger(val: any) {
  if (Number.isInteger) {
    return Number.isInteger(val);
  }
  return typeof val === 'number' && isFinite(val) && Math.floor(val) === val;
}

function isArray(val: any) {
  if (Array.isArray) {
    return Array.isArray(val);
  }
  return Object.prototype.toString.call(val) === '[object Array]';
}

function ObjectAssign(a: any, b: any, c: any) {
  if (Object.assign) {
    // return Object.assign.apply(null, arguments);
    return Object.assign.apply(null, arguments as any);
  }

  const target: any = {};
  for (let index = 1; index < arguments.length; index++) {
    const source = arguments[index];
    if (source != null) {
      for (const key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
          target[key] = source[key];
        }
      }
    }
  }
  return target;
}

function getTimestamp(msec: any) {
  msec = !msec && msec !== 0 ? msec : 1;
  return parseInt((new Date().valueOf() / msec).toString(), 10);
}

// 降级方案
function normalizeFallbackConfig(customConfig: any) {
  const siteProtocol = window.location.protocol.replace(':', '');
  const defaultConf: any = {
    protocol: siteProtocol === 'http' ? 'http' : 'https',
    lang: 'zh-CN',
    errorFallbackCount: 3,
  };
  const config: any = ObjectAssign({}, defaultConf, customConfig);

  const errorFallbackCount: any = config.errorFallbackCount;
  assert(
    errorFallbackCount === undefined || (isInteger(errorFallbackCount) && errorFallbackCount >= 1),
    "errorFallbackCount must be an integer, and it's value greater than or equal one",
  );

  return config;
}

function loadResource(config: any, cb: any) {
  if ((window as any).initNECaptcha) {
    return cb(null);
  }
  function genUrl(server: any) {
    const path = 'load.min.js';
    let _urls = [];
    if (isArray(server)) {
      for (let i = 0, len = server.length; i < len; i++) {
        _urls.push(joinUrl(config.protocol, server[i], path));
      }
    } else {
      const url = joinUrl(config.protocol, server, path);
      _urls = [url, url];
    }

    return _urls;
  }
  const urls = genUrl(config.staticServer || ['cstaticdun.126.net', 'cstaticdun1.126.net', 'cstatic.dun.163yun.com']);

  function step(i: any) {
    const url = urls[i] + '?v=' + getTimestamp(CACHE_MIN);
    loadScript(url, function (err: any) {
      if (err || !(window as any).initNECaptcha) {
        // loadjs的全局变量
        i = i + 1;
        if (i === urls.length) {
          return cb(new Error('Failed to load script(' + url + ').' + (err ? err.message : 'unreliable script')));
        }
        return step(i);
      }
      return cb(null);
    });
  }
  step(0);
}

/*
 * entry: initNECaptchaWithFallback
 * options:
 *  errorFallbackCount: 触发降级的错误次数，默认第三次错误降级
 *  defaultFallback: 是否开启默认降级
 *  onFallback: 自定义降级方案，参数为默认validate
 */
export function initNECaptchaWithFallback(options: any, onload: any, onerror: any) {
  let captchaIns: any = null;

  const config = normalizeFallbackConfig(options);
  const defaultFallback = config.defaultFallback !== false;
  const langPkg = FALLBACK_LANG[config.lang === 'zh-CN' ? config.lang : 'en'];
  const storeKey = window.location.pathname + '_' + config.captchaId + '_NECAPTCHA_ERROR_COUNTS';
  try {
    errorCallbackCount = parseInt(localStorage.getItem(storeKey)?.toString() || '0', 10);
  } catch (error) {}

  const fallbackFn = !defaultFallback
    ? config.onFallback || function () {}
    : (validate: any) => {
        function setFallbackTip(instance: any) {
          if (!instance) {
            return;
          }
          setFallbackTip(instance._captchaIns);
          if (!instance.$el) {
            return;
          }
          const tipEles = queryAllByClassName('.yidun-fallback__tip', instance.$el);
          if (!tipEles.length) {
            return;
          }

          // 确保在队列的最后
          setTimeout(() => {
            for (let i = 0, l = tipEles.length; i < l; i++) {
              setDomText(tipEles[i], langPkg);
            }
          }, 0);
        }
        setFallbackTip(captchaIns);

        config.onVerify && config.onVerify(null, { validate: validate });
      };
  const noFallback = !defaultFallback && !config.onFallback;

  const proxyOnError = (error: any) => {
    errorCallbackCount++;
    if (errorCallbackCount < config.errorFallbackCount) {
      try {
        localStorage.setItem(storeKey, errorCallbackCount);
      } catch (err) {}

      onerror(error);
    } else {
      fallbackFn(DEFAULT_VALIDATE);
      proxyRefresh();
      noFallback && onerror(error);
    }
  };

  const proxyRefresh = () => {
    errorCallbackCount = 0;
    try {
      localStorage.setItem(storeKey, '0');
    } catch (err) {}
  };

  const triggerInitError = (error: any) => {
    if (initialTimer && initialTimer.isError()) {
      initialTimer.resetError();
      return;
    }
    initialTimer && initialTimer.resetTimer();
    noFallback ? onerror(error) : proxyOnError(error);
  };

  config.onError = (error: any) => {
    if (initialTimer && initialTimer.isError()) {
      initialTimer.resetError();
    }
    proxyOnError(error);
  };
  config.onDidRefresh = () => {
    if (initialTimer && initialTimer.isError()) {
      initialTimer.resetError();
    }
    proxyRefresh();
  };

  const initialTimer = options.initTimeoutError ? options.initTimeoutError(proxyOnError) : null; // initialTimer is only for mobile.html

  const loadResolve = () => {
    (window as any).initNECaptcha(
      config,
      (instance: any) => {
        if (initialTimer && initialTimer.isError()) {
          return;
        }
        initialTimer && initialTimer.resetTimer();
        captchaIns = instance;
        onload && onload(instance);
      },
      triggerInitError,
    );
  };
  const cacheId = 'load-queue';
  if (!RESOURCE_CACHE[cacheId]) {
    RESOURCE_CACHE[cacheId] = {
      rejects: [],
      resolves: [],
      status: 'error',
    };
  }
  if (RESOURCE_CACHE[cacheId].status === 'error') {
    RESOURCE_CACHE[cacheId].status = 'pending';
    loadResource(config, (error: any) => {
      if (error) {
        const err: any = new Error();
        err.code = REQUEST_SCRIPT_ERROR;
        err.message = config.staticServer + '/load.min.js error';

        const rejects = RESOURCE_CACHE[cacheId].rejects;
        for (let i = 0, iLen = rejects.length; i < iLen; i++) {
          rejects.pop()(err);
        }
        RESOURCE_CACHE[cacheId].status = 'error';
      } else {
        RESOURCE_CACHE[cacheId].status = 'done';
        const resolves = RESOURCE_CACHE[cacheId].resolves;
        for (let j = 0, jLen = resolves.length; j < jLen; j++) {
          resolves.pop()();
        }
      }
    });
  } else if (RESOURCE_CACHE[cacheId].status === 'done') {
    loadResolve();
  }
  if (RESOURCE_CACHE[cacheId].status === 'pending') {
    RESOURCE_CACHE[cacheId].rejects.push(function loadReject(err: any) {
      triggerInitError(err);
    });
    RESOURCE_CACHE[cacheId].resolves.push(loadResolve);
  }
}
